package com.unicom.si.security.config;

import com.unicom.si.security.decide.MyAccessDecisionManager;
import com.unicom.si.security.filter.MyAnonymousAuthenticationFilter;
import com.unicom.si.security.filter.MyUsernamePasswordAuthenticationFilter;
import com.unicom.si.security.metadata.MyFilterInvocationSecurityMetadataSource;
import com.unicom.si.security.service.CustomerUserDetailsService;
import com.unicom.si.security.utils.CustomPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;

import java.util.UUID;

@EnableWebSecurity
/**
 *  三种方法级权限控制
 * @author chenxu49
 * 1.securedEnabled: Spring Security’s native annotation
 * 2.jsr250Enabled: standards-based and allow simple role-based constraints
 * 3.prePostEnabled: expression-based 也就是spring的EL表达式
 */
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public CustomerUserDetailsService customerUserDetails;

    @Override
    protected UserDetailsService userDetailsService() {
        return super.userDetailsService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
//        daoProvider.setPasswordEncoder(new CustomPasswordEncoder());
        daoProvider.setUserDetailsService(customerUserDetails);

        auth.authenticationProvider(daoProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .anyRequest().authenticated()
                // 下边这些antMatchers测试这不起作用，真正起作用的是MyFilterInvocationSecurityMetadataSource里边获取到的请求
                // 另外方法级别的权限验证，和URL的验证不是一套方法
                // 感觉好像用了自定义的FilterSecurityInterceptor，下边这些就不起作用了
//                    .antMatchers("/loginPage.jsp","/getCode","/test/**","/error.jsp").permitAll()
//                    .antMatchers("/resources/**").permitAll()
//                    .antMatchers("/wei/**").access("hasRole('ROLE_WEI')")
//                    .anyRequest().authenticated().fullyAuthenticated()
////                .authenticated()
                    .expressionHandler(this.getExpressionHandler())

                .and()
//                .anonymous().authenticationFilter(new MyAnonymousAuthenticationFilter(UUID.randomUUID().toString()))
                .csrf().disable() // 关闭CSRF，不然post请求会被拦截，被认为是403
//                .and()
//                .formLogin()
//                    .loginPage("/loginPage.jsp")
//                    .usernameParameter("username")
//                    .passwordParameter("password")
//                    .successHandler(this.successHandler())
//                    .failureHandler(this.failureHandler())
//                    .successForwardUrl("/welcome")
//                    .failureForwardUrl("/error.jsp")

//                .and()
                .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessUrl("/loginPage.jsp")
                    .invalidateHttpSession(true)
                .and()
                .exceptionHandling()
                    .authenticationEntryPoint(this.getEntryPoint())// 设置这个是为了能找到entry point，登录入口
                .and()
                .addFilterBefore(this.customeFilterSecurityInterceptor(), FilterSecurityInterceptor.class)
                .addFilterAt(this.customeLoginFilter(), UsernamePasswordAuthenticationFilter.class)
//                .addFilterAt(this.getExceptionFilter(), ExceptionTranslationFilter.class)

                // session 并发管理好像没起作用，以后再看吧
                .sessionManagement()
                    .sessionAuthenticationErrorUrl("/loginPage.jsp")
                    .maximumSessions(1) // 同一个用户只允许一个session
                    .maxSessionsPreventsLogin(false) //false，第二个人登陆，第一个人退出。true 第二个人不允许登录
                    .expiredUrl("/loginPage.jsp") // session过期跳转页
                    .sessionRegistry(this.getSessionRegistry()) // session 仓库
                    .expiredSessionStrategy(new SimpleRedirectSessionInformationExpiredStrategy("/"))

//                    .and()
//                .and()
//                .httpBasic();
        ;

    }

    // 静态资源的没测试
    @Override
    public void configure(WebSecurity web) throws Exception {
//        super.configure(web);
        web.ignoring().antMatchers("/loginPage.jsp");
    }

    // 添加这个bean，是为了解决基于方法注解的@PreAuthorize("hasRole('dept')")。权限的前缀可以不是以ROLE_开头
    // 因为GlobalMethodSecurityConfiguration中
//    GrantedAuthorityDefaults grantedAuthorityDefaults = (GrantedAuthorityDefaults)this.getSingleBeanOrNull(GrantedAuthorityDefaults.class);
//        if (grantedAuthorityDefaults != null) {
//        this.defaultMethodExpressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
//    }
//    会从spring的根容器里，找一个classType是GrantedAuthorityDefaults的bean，这个bean唯一做的就是定义prefix
    @Bean
    public GrantedAuthorityDefaults getGrantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults("");
    }

    @Bean
    public SessionRegistry getSessionRegistry() {
        return new SessionRegistryImpl();
    }


    // 主要是把自己实现的权限，用户、鉴权器注入进去
    // 权限认证的filter
    @Bean
    public FilterSecurityInterceptor customeFilterSecurityInterceptor() throws Exception {
        FilterSecurityInterceptor customeInterceptor = new FilterSecurityInterceptor();
        customeInterceptor.setAuthenticationManager(this.authenticationManagerBean());
        customeInterceptor.setSecurityMetadataSource(this.getMetadata());
        customeInterceptor.setAccessDecisionManager(this.getAccessDecisionManager());

        return customeInterceptor;
    }

    // 重写authenticationManagerBean()，以让AuthenticationManager以一个bean的方式暴露
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    // form表单验证成功，执行的successHandler
    @Bean
    public SavedRequestAwareAuthenticationSuccessHandler successHandler() {
        SavedRequestAwareAuthenticationSuccessHandler successHandler =
                new SavedRequestAwareAuthenticationSuccessHandler();

        successHandler.setAlwaysUseDefaultTargetUrl(true);
        successHandler.setDefaultTargetUrl("/welcome");

        return successHandler;
    }

    // form表单验证失败执行的handler
    @Bean
    public SimpleUrlAuthenticationFailureHandler failureHandler() {
        SimpleUrlAuthenticationFailureHandler failureHandler =
                new SimpleUrlAuthenticationFailureHandler();

        failureHandler.setDefaultFailureUrl("/error.jsp");

        return failureHandler;
    }

    //自定义 form表单验证，处理登录的filter
    @Bean
    public UsernamePasswordAuthenticationFilter customeLoginFilter() throws Exception {
        MyUsernamePasswordAuthenticationFilter customeLoginFilter =
                new MyUsernamePasswordAuthenticationFilter();

        customeLoginFilter.setFilterProcessesUrl("/login");
        customeLoginFilter.setAuthenticationManager(this.authenticationManagerBean());
        customeLoginFilter.setAuthenticationSuccessHandler(this.successHandler());
        customeLoginFilter.setAuthenticationFailureHandler(this.failureHandler());
//        customeLoginFilter.setRememberMeServices();

        return customeLoginFilter;
    }

    // 初始化和获取权限的元数据
    @Bean
    public FilterInvocationSecurityMetadataSource getMetadata() {
        return new MyFilterInvocationSecurityMetadataSource();
    }

    // 获取决策器
    @Bean
    public AccessDecisionManager getAccessDecisionManager() {
        return new MyAccessDecisionManager();
    }

    // 表达式解析器
    @Bean
    public DefaultWebSecurityExpressionHandler getExpressionHandler() {
        return new DefaultWebSecurityExpressionHandler();
    }

    // 登录入口。在出现无权限异常时，会调用，并返回登录页
    @Bean
    public AuthenticationEntryPoint getEntryPoint() {
        return new LoginUrlAuthenticationEntryPoint("/loginPage.jsp");
    }

//    @Bean
//    public ExceptionTranslationFilter getExceptionFilter() {
//        AuthenticationEntryPoint entryPoint = new LoginUrlAuthenticationEntryPoint("/loginPage.jsp");
//        ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint);
//
//        return exceptionTranslationFilter;
//    }
}
